package com.zhaoyd.redislock.core.lock;

import com.zhaoyd.redislock.util.AssertUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * 注解AOP实现
 * @author zhaoyd
 * @date 2020-05-21
 */
@Aspect
@Component
public class SyncLockHandle {
    @Resource
    private SyncLockFactory syncLockFactory;

    /**
     * 在方法上执行同步锁
     */
    @Around("@annotation(syncLockable)")
    public Object around(ProceedingJoinPoint point, SyncLockable syncLockable) throws Throwable{
        String key = syncLockable.key();
        Long expire = syncLockable.expire();
        key = this.getKey(key, point);
        SyncLock lock = syncLockFactory.build(key, expire, null);
        try {
            boolean isLock = lock.lock();
            AssertUtils.isTrue(isLock, "获取锁失败");
            return point.proceed();
        } finally {
            lock.unLock();
        }
    }

    @Around("@annotation(syncLockableNow)")
    public Object around(ProceedingJoinPoint point, SyncLockableNow syncLockableNow) throws Throwable{
        String key = syncLockableNow.key();
        Long expire = syncLockableNow.expire();
        key = this.getKey(key, point);
        SyncLock lock = syncLockFactory.build(key, expire, null);
        try {
            boolean isLock = lock.lockNow();
            AssertUtils.isTrue(isLock, "获取锁失败，有并发");
            return point.proceed();
        } finally {
            lock.unLock();
        }
    }

    /**
     * 获得key
     * @param key
     * @param point
     * @return
     * @throws NoSuchMethodException
     */
    private String getKey(String key, ProceedingJoinPoint point) throws NoSuchMethodException{
        if(StringUtils.isBlank(key)){
            key = this.getDetaultKey(point);
        }else{
            //解析el表达式
            key = this.parseEL(key, point);
        }
        return key;
    }

    /**
     * 获取className+methodName
     * @param point
     * @return
     * @throws NoSuchMethodException
     */
    private String getDetaultKey(ProceedingJoinPoint point) throws NoSuchMethodException{
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();
        String className = target.getClass().getName();
        return className + "-" + methodName;
    }

    /**
     * 解析el表达式
     * @param key
     * @param point
     * @return
     */
    private String parseEL(String key, ProceedingJoinPoint point){
        //解析el表达式，将#id等替换为参数值
        ExpressionParser expressionParser = new SpelExpressionParser();
        //el表达式需要用#{}包裹
        Expression expression = expressionParser.parseExpression(key, ParserContext.TEMPLATE_EXPRESSION);
        EvaluationContext context = new StandardEvaluationContext();
        String[] parameterNames = ((MethodSignature)point.getSignature()).getParameterNames();
        Object[] args = point.getArgs();
        if(parameterNames==null || parameterNames.length<=0){
            return key;
        }
        for (int i = 0; i <parameterNames.length ; i++) {
            context.setVariable(parameterNames[i],args[i]);
        }
        key = expression.getValue(context).toString();
        return key;
    }

}
